package com.zoe.app.completeFuture_入门;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Console;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.zoe.app.util.SmallTool;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author zoe.Zhang
 * @date 2022/07/22
 * @description B_SupplierAsync
 */
public class B_thenApplyAsync {
    @Test
    public void T1() {
        SmallTool.printTimeAndThread("小明吃完饭了...准备回家");
        SmallTool.printTimeAndThread("小白开始付款");

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员收到尾款");
            ThreadUtil.sleep(1 * 1000); // 线程睡眠 0.5 秒
            SmallTool.printTimeAndThread("服务员开具发票...");
            return "服务员开出了发票";
        });

        SmallTool.printTimeAndThread("小明接了一个电话....约会晚会开黑");
        SmallTool.printTimeAndThread(StrUtil.format("{},小明收到了发票", completableFuture.join()));
    }

    @Test
    public void T2() {
        ExecutorService executor = ThreadUtil.newExecutor(2);
        SmallTool.printTimeAndThread("小明吃完饭了...准备回家");
        SmallTool.printTimeAndThread("小白开始付款");

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("服务员A收到尾款");
            ThreadUtil.sleep(1 * 1000); // 线程睡眠 0.5 秒
            return "500";
        }).thenApplyAsync(res -> {
            SmallTool.printTimeAndThread(StrUtil.format("服务员B收到尾款{},服务员B开具发票...", res));
            ThreadUtil.sleep(2 * 1000); // 线程睡眠 0.2 秒
            return "服务员B开出了发票" + res;
        });

        SmallTool.printTimeAndThread("小明接了一个电话....约会晚会开黑");
        SmallTool.printTimeAndThread(StrUtil.format("{},小明收到了发票", completableFuture.join()));
    }

    /**
     * Either 任意一个
     * applyToEither，把上一个CompletableFuture任务和本次的CompletableFuture任务并行执行，那个先执行完，哪个就作为结果返回
     */
    @Test
    public void T3() {
        SmallTool.printTimeAndThread("小白开始坐公交回家...");
        SmallTool.printTimeAndThread("小白等待 公交车,700或者800路工具,哪个先来坐哪个");

        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("700路公交车正在路上...");
            ThreadUtil.sleep(2 * 1000); // 线程睡眠 0.5 秒
            return "700";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("800路公交车正在赶来...");
            ThreadUtil.sleep(3 * 1000); // 线程睡眠 0.3 秒
            return "800";
        }), firstBus -> firstBus);

        String join = completableFuture.join();
        SmallTool.printTimeAndThread(StrUtil.format("{}路公交车来了...小明坐车回家了", join));
    }

    /**
     * exceptionally 前面有任意一个流程出现了异常，都会走到最近的一个exceptionally里面，然后执行exceptionally里面的逻辑
     */
    @Test
    public void T4() {
        ExecutorService executor = ThreadUtil.newExecutor(5);
        SmallTool.printTimeAndThread("小白开始坐公交回家...");
        SmallTool.printTimeAndThread("小白等待 公交车,700或者800路工具,哪个先来坐哪个");

        CompletableFuture<String> exceptionally = CompletableFuture.runAsync(() -> {
            SmallTool.printTimeAndThread("小白来到了公交站台...");
            // throw new RuntimeException("公交站台坏了");
        }).thenApplyAsync((res) -> {
            SmallTool.printTimeAndThread("700路公交车正在路上...");
            ThreadUtil.sleep(2 * 1000); // 线程睡眠 0.5 秒
            return "700";
        }).exceptionally((e) -> {
            e.printStackTrace();
            return "出租车";
        }).applyToEither(
                CompletableFuture.supplyAsync(() -> {
                    SmallTool.printTimeAndThread("800路公交车正在赶来...");
                    ThreadUtil.sleep(3 * 1000); // 线程睡眠 0.3 秒
                    return "800";
                }),
                firstBus -> {
                    Assert.isTrue(firstBus.equals("800"), "700公交车撞了...");
                    return firstBus;
                }
        ).exceptionally(e -> {
            e.printStackTrace();
            return "飞机";
        });
        String join = exceptionally.join();
        SmallTool.printTimeAndThread(StrUtil.format("{},小明开始回家啦...", join));
    }

    @Test
    public void T5() {
        SmallTool.printTimeAndThread("小明回家了准备坐公交");
        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            SmallTool.printTimeAndThread("700公交正在路上...");
            ThreadUtil.sleep(3 * 1000); // 线程睡眠 3 秒

            return "700";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
                    SmallTool.printTimeAndThread("800公交正在路上...");
                    ThreadUtil.sleep(2 * 1000); // 线程睡眠 2 秒

                    return "800";
                }), fistBus -> fistBus
        ).handle((res, error) -> {
            if (ObjectUtil.isNotNull(error)) {
                return "飞机";
            }
            Assert.isTrue(StrUtil.equals(res, "800"), "700公交车有问题");
            return res;
        }).whenComplete((res, error) -> {
            Console.log("正常结果:" + res);
            Console.log("异常结果" + error);
        });
        String join = stringCompletableFuture.join();
        if (ObjectUtil.isNull(join)) {
            SmallTool.printTimeAndThread("小明走路回家");
        }
        else {
            SmallTool.printTimeAndThread(StrUtil.format("{},回家", join));
        }

    }

    @Test
    public void T6() {
        ArrayList<Dish> dishes = CollUtil.newArrayList();
        for (int i = 0; i < 10; i++) {
            Dish dish = new Dish("菜" + i, 1);
            dishes.add(dish);
        }

        TimeInterval timer = DateUtil.timer();
        timer.start();
        for (Dish dish : dishes) {
            // 切记：不能在for循环里面调用异步任务的join方法，不然会把这些任务串行化。达不到异步的效果，需要把异步任务添加到一个集合里面，在处理，看T7()这个方法
            CompletableFuture.runAsync(dish::make).join();
        }
        long endSecond = timer.intervalSecond();
        System.out.println("耗时:" + endSecond);
    }

    @Test
    public void T7() {
        ArrayList<Dish> dishes = CollUtil.newArrayList();
        for (int i = 0; i < 15; i++) {
            Dish dish = new Dish("菜" + i, 1);
            dishes.add(dish);
        }
        TimeInterval timer = DateUtil.timer();
        timer.start();
        ArrayList<CompletableFuture<String>> completableFutures = CollUtil.newArrayList();
        for (Dish dish : dishes) {
            CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(dish::make);
            // 把异步任务添加到异步任务集合里面，再统一处理
            completableFutures.add(voidCompletableFuture);
        }

        CompletableFuture[] completableFuturesArr = completableFutures.stream()
                                                                      .toArray(CompletableFuture[]::new);
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(completableFuturesArr);
        List<String> result = voidCompletableFuture.thenApply(v -> completableFutures.stream()
                                                                                     .map(CompletableFuture::join)
                                                                                     .collect(Collectors.toList())
                                                   )
                                                   .join();
        System.out.println(result);
        long endSecond = timer.intervalSecond();
        System.out.println("耗时:" + endSecond);

    }

    @Test
    public void T8() {
        // IntStream.range(0, 10) 相当于fori循环
        CompletableFuture[] completableFutures = IntStream.range(0, 10)
                                                          .mapToObj(i -> new Dish("菜" + i, 1))
                                                          .map(dish -> CompletableFuture.runAsync(dish::make))
                                                          .toArray(CompletableFuture[]::new);
        TimeInterval timer = DateUtil.timer();
        timer.start();

        CompletableFuture.allOf(completableFutures).join();

        long endSecond = timer.intervalSecond();
        System.out.println("耗时:" + endSecond);
    }

}
